// USAGE:     ./scenerender objects/ O -2 2 -2 2 -2 10


// main.cpp

#define GL3_PROTOTYPES

// Include the vector and matrix utilities from the textbook, as well as some
// macro definitions.
#include "Angel.h"

#ifdef __APPLE__
#  include <OpenGL/gl3.h>
#endif

#include <typeinfo>

#include "Angel.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fstream>
#include <iostream>
#include <string>
#include <stdlib.h>
#include <dirent.h>
#include <boost/algorithm/string.hpp>
#include <vector>

#include "DebugHelper.h"
#include "ConcreteGLObject3D.h"
#include "GLScene.h"

// vv need these here for the moment to compile without a bunch of changes
string scenePath;
typedef Angel::vec4  color4;
typedef Angel::vec4  point4;
GLuint  model_view;  // model-view matrix uniform shader variable location
GLuint  projection; // projection matrix uniform shader variable location
mat4 model, view, mv,transformed, ident; //model matrix, view matrix, combined model-view matrix

//Variables for the menus
int objectSubMenu;
int sceneSubMenu;
int sceneRotationSubMenu;
int loadObjectsMenu;
int shaderMenu;
vector <string> objectNames;

//Variables for selection
int objectID = 200;
int picked = -1;
GLint flag = 0;

//Variables for mouse movement
int lastX, lastY = 0;
int mouseDown;

//Variables for model transforms
int transformMode = 0;
int modelMode = 0;
int sceneMode = 0;

//Variables for scene transforms
point4 eye;
point4 at;
vec4 up;

//variables for reshape
mat4 p;
char **g_argv;
// ^^ need these here for the moment to compile without a bunch of changes

// A global constant for the number of points that will be in our object.
using namespace std;
using namespace boost;
using namespace Voltron;

void loadObjectMenu(int value);

// globals
GLuint shaderprogram;
GLuint phongShaderProgram;
GLuint toonShaderProgram;
GLScene *_scene;
GLObject3D* globj;
float shades = 4;
point4 light_position( 1.5, 0.5, 2.0, 1.0 );
float  material_shininess = 100.0;
color4 ambient_product;
color4 diffuse_product;
color4 specular_product;
//----------------------------------------------------------------------------
void init()
{
    _scene = new GLScene();
    toonShaderProgram = InitShader("toon_vshader.glsl", "toon_fshader.glsl");
    phongShaderProgram = InitShader("phong_vshader.glsl", "phong_fshader.glsl");
    shaderprogram = phongShaderProgram;

//// vvv THIS STUFF NEEDS TO BE WRAPPED BETTER
///////////////////////////////////////////////////////////////////////

    // Initialize shader lighting parameters
    // RAM: No need to change these...we'll learn about the details when we
    // cover Illumination and Shading
    color4 light_ambient( 0.0, 0.0, 0.0, 1.0 );
    color4 light_diffuse( 1.0, 1.0, 1.0, 1.0 );
    color4 light_specular( 1.0, 1.0, 1.0, 1.0 );

    color4 material_ambient( 1.0, 1.0, 1.0, 1.0 );
    color4 material_diffuse( 1.0, 0.8, 0.0, 1.0 );
    color4 material_specular( 0.3, 0.3, 0.3, 1.0 );

    ambient_product = light_ambient * material_ambient;
    diffuse_product = light_diffuse * material_diffuse;
    specular_product = light_specular * material_specular;

    glUniform4fv( glGetUniformLocation(shaderprogram, "AmbientProduct"),
		  1, ambient_product );
    glUniform4fv( glGetUniformLocation(shaderprogram, "DiffuseProduct"),
		  1, diffuse_product );
    glUniform4fv( glGetUniformLocation(shaderprogram, "SpecularProduct"),
		  1, specular_product );

    glUniform4fv( glGetUniformLocation(shaderprogram, "LightPosition"),
		  1, light_position );

    glUniform1f( glGetUniformLocation(shaderprogram, "Shininess"),
		 material_shininess );

    glEnable( GL_DEPTH_TEST );

///////////////////////////////////////////////////////////////////////
//// ^^^ THIS STUFF NEEDS TO BE WRAPPED BETTER
///////////////////////////////////////////////////////////////////////

	// black background
    glClearColor( 1.0, 1.0, 1.0, 1.0 );
}

//----------------------------------------------------------------------------
void initView(char *argv[])
{
    model_view = glGetUniformLocation( shaderprogram, "ModelView");
    projection = glGetUniformLocation( shaderprogram, "Projection");

    if(string(argv[2]) == "O")
    {
        p =  Ortho(atof(argv[3]),atof(argv[4]),atof(argv[5]),atof(argv[6]),atof(argv[7]),atof(argv[8]));
    }

    else if(string(argv[2]) == "P")
    {
        p = Perspective(atof(argv[3]),1.0,atof(argv[4]),atof(argv[5]));
    }
    else
    {
        cout << "Not a valid command" << endl;
        exit(-1);
    }

    eye = point4( 0, 0, 3, 1.0);
    at = point4( 0, 0, 0, 1.0);
    up = vec4( 0, 2, 0, 0.0);

    view = LookAt( eye, at, up );
    mv = view;

    glUniformMatrix4fv( model_view, 1, GL_TRUE, mv );
    glUniformMatrix4fv( projection, 1, GL_TRUE, p );
}

//----------------------------------------------------------------------------
void updateProjection(char *argv[],float aspectRatio)
{
    projection = glGetUniformLocation( shaderprogram, "Projection");

    if(string(argv[2]) == "O")
    {
        p =  Ortho(atof(argv[3])*aspectRatio,atof(argv[4])*aspectRatio,atof(argv[5])*aspectRatio,atof(argv[6])*aspectRatio,atof(argv[7]),atof(argv[8]));
    }

    else if(string(argv[2]) == "P")
    {
        p = Perspective(atof(argv[3]),aspectRatio,atof(argv[4]),atof(argv[5]));
    }
    glUniformMatrix4fv( projection, 1, GL_TRUE, p );
}

//----------------------------------------------------------------------------
void display(void)
{
    // clear the window
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	 for (int i=0;i < _scene->_sceneObjects->size();i++){
	     if(picked == i){
                (*(_scene->_sceneObjects))[i]->setDrawMode(GL_LINE);
	     }else{
                (*(_scene->_sceneObjects))[i]->setDrawMode(GL_FILL);
	     }

            glUniformMatrix4fv( model_view, 1, GL_TRUE, (*(_scene->_sceneObjects))[i]->_model);
            (*(_scene->_sceneObjects))[i]->render();
	 }
    glFlush();
    glutSwapBuffers();
    glutPostRedisplay();
}

//----------------------------------------------------------------------------
void keyboard(unsigned char key, int x, int y)
{
    switch (key) {

    // Quit when ESC is pressed
    case 27:
        exit(EXIT_SUCCESS);
        break;
        
    		case (int)('1'):
	        loadObjectMenu(1);
	        break;

		case (int)('2'):
	        loadObjectMenu(2);
	        break;

    }
}

//----------------------------------------------------------------------------
void specialKey(int key, int x, int y){
    switch(key){
    case GLUT_KEY_UP:
        if (modelMode++ > 3)
            modelMode = 0;
        break;

    case GLUT_KEY_RIGHT:
        if(shaderprogram == toonShaderProgram){
            shades++;
            glUniform1f( glGetUniformLocation(shaderprogram, "Shades"),
                        shades );
        }
        break;
    case GLUT_KEY_LEFT:
        if(shaderprogram == toonShaderProgram){
            shades--;
            glUniform1f( glGetUniformLocation(shaderprogram, "Shades"),
                        shades );
        }
        break;
    }
    cout << "Numshades: " << shades << endl;

}

//----------------------------------------------------------------------------
mat4 translate(int mode, int dx, int dy)
{
    mat4 modelTransform;
    switch(mode){
        case 0:
            modelTransform = Translate(float(-dx) * .1,0,0);
            break;
        case 1:
            modelTransform = Translate(0, float(dy) * .1, 0);
            break;
        case 2:
            modelTransform = Translate(0,0,float(dx) * .1);
            break;
    }
    return modelTransform;
}

//----------------------------------------------------------------------------
mat4 rotate(int mode, int dx, int dy)
{
    mat4 modelTransform;
    switch(mode){
        case 0:
            modelTransform = RotateX(float(-dy) * .1);
            break;
        case 1:
            modelTransform = RotateY(float(-dx) * .1);
            break;
        case 2:
            modelTransform = RotateZ(float(-dx) * .1);
            break;
    }
    return modelTransform;
}

//----------------------------------------------------------------------------
mat4 scale(int mode, int dx, int dy)
{
    mat4 modelTransform;
    switch(mode){
        case 0:
            modelTransform = Scale(1 + (float(dx) * .1),1,1);
            break;
        case 1:
            modelTransform = Scale(1, 1 + (float(dy) * .1), 1);
            break;
        case 2:
            modelTransform = Scale(1, 1, 1+ (float(dx) * .1));
            break;
    }
    return modelTransform;
}

//----------------------------------------------------------------------------
mat4 pan(int dx, int dy)
{
    mat4 modelTransform;
    modelTransform = Translate(float(dx) * .1,0,0);
    return modelTransform;
}

//----------------------------------------------------------------------------
mat4 sceneRotate(int mode, int dx, int dy)
{
    mat4 modelTransform;
    switch(mode){
        case 0:
            modelTransform = RotateX(float(dy) * .1);
            break;
        case 1:
            modelTransform = RotateY(float(dx) * .1);
            break;
        case 2:
            modelTransform = RotateZ(float(dx) * .1);
            break;
    }
    return modelTransform;
}

//----------------------------------------------------------------------------
mat4 dolly(int dx, int dy)
{
    mat4 modelTransform;
    modelTransform = Translate(0,0,float(-dy) *.1);
    return modelTransform;
}
//----------------------------------------------------------------------------
void mouse(int button, int state, int x, int y)
{
    printf("Mouse button pressed at %d, %d\n", x, y);
    lastX = x;
    lastY = y;
    mouseDown = (state == GLUT_DOWN);

    GLubyte pixel[4];
    GLint viewport[4];
    glGetIntegerv(GL_VIEWPORT,viewport);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    for (int i=0;i < _scene->_sceneObjects->size();i++){
        (*(_scene->_sceneObjects))[i]->renderSelectionColor();
    }

    glReadPixels(x,viewport[3] - y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
    picked = -1;

    for (int i=0;i < _scene->_sceneObjects->size();i++){
        if((*(_scene->_sceneObjects))[i]->isSelected(pixel)){
            picked = i;
        }
    }
}

//----------------------------------------------------------------------------
void mouseMovement(int x,int y)
{
    if(mouseDown)
    {
        int dx = lastX - x;
        int dy = lastY - y;

        for (int i=0;i < _scene->_sceneObjects->size();i++){
            switch(transformMode){
                case 0:
                    model = translate(modelMode,dx,dy);
                    if(picked == i)
                        (*(_scene->_sceneObjects))[i]->transformModel(model);
                    break;
                case 1:
                    model = rotate(modelMode,dx,dy);
                    if(picked == i)
                        (*(_scene->_sceneObjects))[i]->transformModel(model);
                    break;
                case 2:
                    model = scale(modelMode,dx,dy);
                    if(picked == i)
                        (*(_scene->_sceneObjects))[i]->transformModel(model);
                    break;
                case 3:
                    model = pan(dx,dy);
                    (*(_scene->_sceneObjects))[i]->transformModel(model);
                    break;
                case 4:
                    model = dolly(dx,dy);
                    (*(_scene->_sceneObjects))[i]->transformModel(model);
                    break;
                case 5:
                    model = sceneRotate(sceneMode,dx,dy);
                    //(*(_scene->_sceneObjects))[i]->transformModel(inverseTranspose(model));
                    break;
            }
        }
    }
    lastX = x;
    lastY = y;
}

//----------------------------------------------------------------------------
void shadersMenu(int value)
{
    if(value == 0){
        shaderprogram = phongShaderProgram;
        glUseProgram(shaderprogram);
    }
    if(value == 1){
        shaderprogram = toonShaderProgram;
        glUseProgram(shaderprogram);
        glUniform1f( glGetUniformLocation(shaderprogram, "Shades"),shades);
    }
        glUniform4fv( glGetUniformLocation(shaderprogram, "AmbientProduct"),
                    1, ambient_product );
        glUniform4fv( glGetUniformLocation(shaderprogram, "DiffuseProduct"),
                    1, diffuse_product );
        glUniform4fv( glGetUniformLocation(shaderprogram, "SpecularProduct"),
                    1, specular_product );
        glUniform4fv( glGetUniformLocation(shaderprogram, "LightPosition"),
                    1, light_position );
        glUniform1f( glGetUniformLocation(shaderprogram, "Shininess"),
                    material_shininess );
        model_view = glGetUniformLocation(shaderprogram, "ModelView");
        projection = glGetUniformLocation(shaderprogram, "Projection");
        glUniformMatrix4fv( model_view, 1, GL_TRUE, mv );
        glUniformMatrix4fv( projection, 1, GL_TRUE, p );

    glutPostRedisplay();
}

//----------------------------------------------------------------------------
void objectMenu(int value)
{
    if(value == 0){
        exit(0);
    }
    if(value == 1){
        cout << "Object Translation" << endl;
        transformMode = 0;
    }
    if(value == 2){
        cout << "Object Rotation" << endl;
        transformMode = 1;
    }
    if(value == 3){
        cout << "Object Scale" << endl;
        transformMode = 2;
    }
    glutPostRedisplay();
}

//----------------------------------------------------------------------------
void sceneMenu(int value)
{
    if(value == 0){
        exit(0);
    }
    if(value == 1){
        cout << "Scene Translation" << endl;
        transformMode = 3;
    }
    if(value == 2){
        cout << "Scene Rotation" << endl;
    }
    if(value == 3){
        cout << "Scene Dolly" << endl;
        transformMode = 4;
    }
    glutPostRedisplay();
}

//----------------------------------------------------------------------------
void sceneRotationMenu(int value)
{
    if(value == 0){
        exit(0);
    }
    if(value == 1){
        cout << "X" << endl;
        transformMode = 5;
        sceneMode = 0;
    }
    if(value == 2){
        cout << "Y" << endl;
        transformMode = 5;
        sceneMode = 1;
    }
    if(value == 3){
        cout << "Z" << endl;
        transformMode = 5;
        sceneMode = 2;
    }
    glutPostRedisplay();
}

//----------------------------------------------------------------------------
void mainMenu(int value)
{
    if(value == 0){
        exit(0);
    }
    if(value == 1){
        cout << "Object Transformation" << endl;
    }
    if(value == 2){
        cout << "Scene Transformation" << endl;
    }
    glutPostRedisplay();
}

//----------------------------------------------------------------------------
//Returns vector of file names
vector <string> getFileNames(const string directory)
{
    DIR *dir;
    class dirent *ent;
    class stat st;
    vector <string> objNames;

    dir = opendir(directory.c_str());
    while ((ent = readdir(dir)) != NULL) {
    	const string file_name = ent->d_name;
    	const string full_file_name = directory + "/" + file_name;

    	const bool is_file = (S_ISREG(st.st_mode)) != 0;
        if (file_name[0] == '.')
                        continue;
        objNames.push_back(full_file_name);
    }
    closedir(dir);
    return objNames;
}

//----------------------------------------------------------------------------
void populateObjectMenu(vector<string> objNames)
{
    for(int i = 0; i< objNames.size(); i++){
        glutAddMenuEntry(objNames[i].c_str(),i);
    }

}

//----------------------------------------------------------------------------
void loadObjectMenu(int value){
    globj = new ConcreteGLObject3D();
    globj->loadGLObjectFromFile(objectNames[value]);
    _scene->addObject(globj);
    cout << (*(_scene->_sceneObjects)).size() << endl;
    (*(_scene->_sceneObjects))[(*(_scene->_sceneObjects)).size()-1]->_selectionColor = vec4(objectID,0,0,1);
    (*(_scene->_sceneObjects))[(*(_scene->_sceneObjects)).size()-1]->initBuffers(shaderprogram);
    (*(_scene->_sceneObjects))[(*(_scene->_sceneObjects)).size()-1]->transformModel(view);
    glUniformMatrix4fv(model_view,1,GL_TRUE,mv);
    (*(_scene->_sceneObjects))[(*(_scene->_sceneObjects)).size()-1]->render();
    objectID++;
    cout << objectNames[value] << endl;
    glutPostRedisplay();
}

//----------------------------------------------------------------------------
void createMenu(void)
{
    objectSubMenu = glutCreateMenu(objectMenu);
    glutAddMenuEntry("Translation",1);
    glutAddMenuEntry("Rotation",2);
    glutAddMenuEntry("Scale",3);

    sceneRotationSubMenu = glutCreateMenu(sceneRotationMenu);
    glutAddMenuEntry("X",1);
    glutAddMenuEntry("Y",2);
    glutAddMenuEntry("Z",3);

    sceneSubMenu = glutCreateMenu(sceneMenu);
    glutAddMenuEntry("Translation",1);
    glutAddSubMenu("Rotation",sceneRotationSubMenu);
    glutAddMenuEntry("Dolly",3);

    loadObjectsMenu = glutCreateMenu(loadObjectMenu);
    populateObjectMenu(objectNames);

    shaderMenu = glutCreateMenu(shadersMenu);
    glutAddMenuEntry("Phong",0);
    glutAddMenuEntry("Cell Shader",1);

    glutCreateMenu(mainMenu);
    glutAddSubMenu("Object Transformation",objectSubMenu);
    glutAddSubMenu("Scene Transformation",sceneSubMenu);
    glutAddSubMenu("Load Objects",loadObjectsMenu);
    glutAddSubMenu("Shaders",shaderMenu);

    glutAttachMenu(GLUT_RIGHT_BUTTON);
}

//----------------------------------------------------------------------------
void reshape(int width,int height)
{
    glViewport(0,0,width,height);
    //updateProjection(g_argv,width/height);
}
//----------------------------------------------------------------------------
int main (int argc, char *argv[])
{
    DEBUG_PRINT("main (int argc, char *argv[])\n");
    DEBUG_PRINT("argc=%d\n",argc);
    g_argv = argv;
    glutInit(&argc,argv);

    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
    glutInitContextVersion(3,2);
    glutInitContextFlags(GLUT_FORWARD_COMPATIBLE);

    glutInitWindowSize(512,512);
    glutInitWindowPosition(500,300);

    glutCreateWindow("CS450-Asgn4-ShaderDemo");

    glewExperimental = GL_TRUE;
    glewInit();

    objectNames = getFileNames(argv[1]);
    init();
    createMenu();

    initView(argv);

    glutKeyboardFunc(keyboard);
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMouseFunc(mouse);
    glutSpecialFunc(specialKey);
    glutMotionFunc(mouseMovement);
    glutMainLoop();

    return 0;
}
